本文僅分享技術心得與效能改進經驗。
核心重點在於如何運用 AI 輔助完成重構與技術選型,純屬開發者個人實驗紀錄。
過去版本(v2.56)使用 Google Apps Script + Google 試算表 當作資料來源。
每次讀取 1 萬筆交易資料時,都要耗時 13 秒以上。
當切換記帳本時,還得再次等待,導致整體體驗極差。
// v2.56 Code.gs - 查詢交易記錄
function getTransactions(bookId) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('transactions');
const data = sheet.getDataRange().getValues(); // 讀取所有資料
const transactions = [];
for (let i = 1; i < data.length; i++) {
const row = data[i];
if (row[1] === bookId) {
transactions.push({
id: row[0],
bookId: row[1],
amount: row[2],
category: row[3],
description: row[4],
date: row[5],
userId: row[6],
});
}
}
transactions.sort((a, b) => new Date(b.date) - new Date(a.date));
return transactions;
}
效能瓶頸分析:
getDataRange().getValues() 每次都抓整張試算表| 資料筆數 | 載入時間 |
|---|---|
| 1,000 筆 | 約 2 秒 |
| 5,000 筆 | 約 6 秒 |
| 10,000 筆 | 約 13 秒 |
| 20,000 筆 | 超過 25 秒 |
📁 v2.56
├── Code.gs
├── Database.gs
├── Utils.gs
├── Report.gs
└── HTML × 20
沒有資料夾結構,找程式碼要靠關鍵字。
沒有型別檢查,容易出現低級錯誤。
HTML Service 不能使用 React / npm,CSS 與 JS 整合困難,手機版不支援 RWD。
當資料量達 1 萬筆時,系統幾乎無法使用。
每次載入都要 13 秒,快取也救不了。
這時候我意識到:
👉 最終決定重寫系統。
| 方案 | 優點 | 缺點 | 結論 |
|---|---|---|---|
| 優化 GAS | 不需重寫 | 效能天花板 | ❌ 放棄 |
| Firebase | 雲端整合佳 | NoSQL 不適合關聯資料 | ❌ 放棄 |
| MERN Stack | 彈性高 | 成本高、部署麻煩 | ❌ 放棄 |
| Next.js + Supabase | 現代化、快速上手 | 需學習新技術 | ✅ 採用 |
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}
// app/transactions/page.tsx
'use client'
import { useEffect, useState } from 'react'
import { createClient } from '@/lib/supabase/client'
export default function TransactionsPage() {
const [transactions, setTransactions] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => { loadTransactions() }, [])
async function loadTransactions() {
const supabase = createClient()
const { data } = await supabase.from('transactions')
.select('*')
.order('date', { ascending: false })
setTransactions(data || [])
setLoading(false)
}
if (loading) return <div>載入中...</div>
return (
<div>
<h1>交易記錄</h1>
{transactions.map(t => (
<div key={t.id}>
{t.date} - {t.description} - ${t.amount}
</div>
))}
</div>
)
}
這次重構幾乎全程由 AI 參與協助,加速至少 70% 開發時間。
| 項目 | AI 輔助 | 我的人工作業 |
|---|---|---|
| 資料表設計 | Schema 初稿 | 審查調整 |
| CRUD API | 自動生成 | 測試與修正 |
| 前端頁面 | 元件雛形 | 美化與邏輯補強 |
| 認證 | Supabase Auth 整合 | 加上邀請制 |
| 分帳邏輯 | 提示演算法 | 驗證與優化 |
// app/api/transactions/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
const { data, error } = await supabase
.from('transactions')
.select('*')
.eq('user_id', user.id)
.order('date', { ascending: false })
if (error) return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json(data)
}
| 指標 | v2.56 (GAS) | v3.38 (Next.js + Supabase) |
|---|---|---|
| 資料載入時間 | 13 秒 | <1 秒 |
| 程式結構 | 扁平 | 模組化 |
| 開發效率 | 低 | 高(AI 協助) |
| 擴充性 | 差 | 高(API + DB) |
| 部署方式 | 手動 | Vercel 自動部署 |